import { type App } from 'vue'
import type { DirectiveBinding } from 'vue'
import '@/assets/drag-resize.scss'

enum PositionMode {
  absolute = 'absolute',
  fixed = 'fixed',
}

const RESIZE_CONFIG = {
  POSITION_MODE: PositionMode.fixed as PositionMode,
  CLASS: {
    HANDLE: 'drag-resize-handle',
    ACTIVE: 'is-active',
    BODY_DRAGGING: (direction: string) => `start-drag-resize__${direction}`,
  },
  DEFAULT_MIN: 0,
  DEFAULT_SIZE: 6,
} as const

type Direction = 'left' | 'right' | 'top' | 'bottom'

interface ResizeDirectiveValue {
  positionMode?: PositionMode
  direction: Direction
  minWidth?: number
  maxWidth?: number
  minHeight?: number
  maxHeight?: number
  mainContainer?: string
  callback?: (width: number, height: number) => void
  disabled?: boolean
  delay?: number
  size?: number
}

interface ResizeElement extends HTMLElement {
  _onResize?: () => void
  _dragEl?: HTMLElement
  _value?: ResizeDirectiveValue
}

// 工具函数：限制值在指定范围内
const clamp = (value: number, min?: number, max?: number): number =>
  Math.max(min || RESIZE_CONFIG.DEFAULT_MIN, Math.min(value, max ?? Infinity))

// 重置拖拽元素位置
const resetDragElement = (el: ResizeElement): void => {
  if (
    !el._dragEl ||
    !el._value ||
    RESIZE_CONFIG.POSITION_MODE != (el._value?.positionMode || PositionMode.fixed)
  )
    return

  const rect = el.getBoundingClientRect()
  const { direction, size = RESIZE_CONFIG.DEFAULT_SIZE } = el._value

  el._dragEl.style.position = RESIZE_CONFIG.POSITION_MODE

  const positions = {
    left: { top: rect.top, left: rect.left, height: rect.height },
    right: { top: rect.top, left: rect.right - size, height: rect.height },
    top: { top: rect.top, left: rect.left, width: rect.width },
    bottom: { top: rect.bottom - size, left: rect.left, width: rect.width },
  } as Record<Direction, Record<keyof CSSStyleDeclaration, number>>

  for (const key in positions[direction]) {
    el._dragEl.style[key] = positions[direction][key] + 'px'
  }
}

/**
 * 更新位置样式
 * @param el 调整的元素
 * @param dragEl 拖拽的元素
 * @param direction 方向
 * @param space 距离
 * @param isFixed 是否固定
 * @param value 参数
 * @param mainRect 主容器的矩形
 * @param size 拖拽元素尺寸
 * @param startWidth 初始宽度
 * @param startHeight 初始高度
 * @param startTop 初始top
 * @param startLeft 初始left
 */
const updateStyle = (
  el: HTMLElement,
  dragEl: HTMLElement,
  direction: string,
  space: number,
  isFixed: boolean,
  value: ResizeDirectiveValue,
  mainRect: DOMRect,
  size: number,
  startWidth: number,
  startHeight: number,
  startTop: number,
  startLeft: number,
) => {
  const rect = el.getBoundingClientRect()
  // eslint-disable-next-line prefer-const
  let { minWidth = 0, maxWidth, minHeight = 0, maxHeight } = value

  if (maxWidth === undefined) {
    maxWidth = mainRect.width
  }
  if (maxHeight === undefined) {
    maxHeight = mainRect.height
  }

  switch (direction) {
    case 'left':
    case 'right':
      el.style.width = `${clamp(startWidth + space, minWidth, maxWidth)}px`
      if (isFixed) {
        const offset = direction === 'left' ? -space : space
        if (direction === 'left') {
          dragEl.style['left'] =
            `${clamp(startLeft + offset, rect.right - maxWidth, rect.right - minWidth)}px`
        } else {
          dragEl.style['left'] =
            `${clamp(startLeft + offset, rect.left + minWidth - size, rect.left + maxWidth - size)}px`
        }
      }
      break
    case 'top':
    case 'bottom':
      el.style.height = `${clamp(startHeight + space, minHeight, maxHeight)}px`
      if (isFixed) {
        const offset = direction === 'top' ? -space : space
        if (direction === 'top') {
          dragEl.style['top'] =
            `${clamp(startTop + offset, rect.bottom - maxHeight, rect.bottom - minHeight)}px`
        } else {
          dragEl.style['top'] =
            `${clamp(startTop + offset, rect.top + minHeight - size, rect.top + maxHeight - size)}px`
        }
      }
      break
  }
}

/**
 * 计算拖拽元素的大小和位置
 * @param e 鼠标事件
 * @param el  调整的元素
 * @param dragEl  拖拽的元素
 * @param direction  方向
 * @param value  参数
 * @param size  拖拽元素尺寸
 * @param isPressDown 是否按下
 * @param startX 初始x
 * @param startY 初始y
 * @param startWidth 初始宽度
 * @param startHeight 初始高度
 * @param startTop 初始top
 * @param startLeft 初始left
 */
const resize = (
  e: MouseEvent,
  el: ResizeElement,
  dragEl: HTMLElement,
  direction: Direction,
  value: ResizeDirectiveValue,
  size: number,
  isPressDown: boolean,
  startX: number,
  startY: number,
  startWidth: number,
  startHeight: number,
  startTop: number,
  startLeft: number,
) => {
  e.preventDefault() // 阻止默认事件
  if (!isPressDown) return
  const isFixed = dragEl.style.position === 'fixed'
  const mainContainer = document.querySelector(value.mainContainer || 'body') as HTMLElement
  const mainRect = mainContainer.getBoundingClientRect()
  const dragElList = mainContainer?.querySelectorAll(`.${RESIZE_CONFIG.CLASS.HANDLE}`)
  let space = 0

  switch (direction) {
    case 'left':
      space = startX - e.clientX
      break
    case 'right':
      space = e.clientX - startX
      break
    case 'top':
      space = startY - e.clientY
      break
    case 'bottom':
      space = e.clientY - startY
      break
  }

  updateStyle(
    el,
    dragEl,
    direction,
    space,
    isFixed,
    value,
    mainRect,
    size,
    startWidth,
    startHeight,
    startTop,
    startLeft,
  )

  if (dragElList?.length) {
    dragElList.forEach((item) => {
      if (item !== dragEl) {
        runAnimation(value?.delay || 0, () => {
          resetDragElement(item.parentElement!) // 更新位置
        }) // 运行动画
      }
    })
  }

  if (value.callback) {
    value.callback(el.offsetWidth, el.offsetHeight)
  }
}

/**
 * 快速切换最大最小尺寸
 * @param el 调整的元素
 * @param value 参数
 */

const toogleSize = (el: ResizeElement, value: ResizeDirectiveValue) => {
  const mainContainer = document.querySelector(value.mainContainer || 'body') as HTMLElement
  const mainRect = mainContainer.getBoundingClientRect()
  const dragElList = mainContainer?.querySelectorAll(`.${RESIZE_CONFIG.CLASS.HANDLE}`)
  // eslint-disable-next-line prefer-const
  let { minWidth = 0, maxWidth, minHeight = 0, maxHeight, direction = 'left' } = value

  if (maxWidth === undefined) {
    maxWidth = mainRect.width
  }
  if (maxHeight === undefined) {
    maxHeight = mainRect.height
  }

  switch (direction) {
    case 'left':
    case 'right':
      el.style.width = `${el.offsetWidth < maxWidth ? maxWidth : minWidth}px`
      break
    case 'top':
    case 'bottom':
      el.style.height = `${el.offsetHeight < maxHeight ? maxHeight : minHeight}px`
      break
  }

  if (dragElList?.length) {
    dragElList.forEach((item) => {
      if (item !== el._dragEl) {
        runAnimation(value?.delay || 0, () => {
          resetDragElement(item.parentElement!) // 更新位置
        }) // 运行动画
      }
    })
  }

  if (value.callback) {
    value.callback(el.offsetWidth, el.offsetHeight)
  }
}

/**
 * 执行动画
 * @param duration 动画持续时间
 * @param runFn 动画函数
 */
const runAnimation = (duration: number, runFn: () => void) => {
  const time = Date.now() + duration + 100
  const animate = () => {
    runFn()
    if (Date.now() > time) {
      return
    }
    window.requestAnimationFrame(animate)
  }
  animate()
}

export function resizeDirective(app: App) {
  app.directive('drag-resize', {
    mounted(el: ResizeElement, binding: DirectiveBinding<ResizeDirectiveValue>) {
      let isPressDown = false
      let startX = 0
      let startY = 0
      let startWidth = 0
      let startHeight = 0
      let startTop = 0
      let startLeft = 0
      let transition = ''
      let isMove = false
      const { value } = binding
      const direction = value.direction || 'left'
      const dragEl = document.createElement('div')
      const size = value.size || RESIZE_CONFIG.DEFAULT_SIZE
      const style = window.getComputedStyle(el)

      if (style.position !== 'absolute') {
        el.style.position = 'relative'
      }

      if (value.size) {
        dragEl.style.setProperty('--handle-size', `${value.size}px`)
      }

      dragEl.classList.add(RESIZE_CONFIG.CLASS.HANDLE, `direction-${direction}`)
      el.appendChild(dragEl) // 将拖拽元素添加到目标元素中
      el._dragEl = dragEl // 保存拖拽元素，方便后续操作
      el._value = value // 保存value，方便后续操作
      transition = `${style.transitionProperty} ${style.transitionDuration} ${style.transitionDelay} ${style.transitionTimingFunction}` // 保存原有的transition属性

      const onMouseMove = (e: MouseEvent) => {
        isMove = true
        resize(
          e,
          el,
          dragEl,
          direction,
          value,
          size,
          isPressDown,
          startX,
          startY,
          startWidth,
          startHeight,
          startTop,
          startLeft,
        )
      }
      const onMouseUp = () => {
        if (el._value?.disabled) return
        isPressDown = false
        dragEl.classList.remove(RESIZE_CONFIG.CLASS.ACTIVE)
        document.body.classList.remove(RESIZE_CONFIG.CLASS.BODY_DRAGGING(direction))

        if (transition) {
          el.style.transition = transition
        }

        if (!isMove && el._value) {
          toogleSize(el, el._value)
        }

        runAnimation(el._value?.delay || 0, () => {
          resetDragElement(el) // 更新位置
        }) // 运行动画

        document.removeEventListener('mousemove', onMouseMove)
        document.removeEventListener('mouseup', onMouseUp)
      }

      const onMouseDown = (e: MouseEvent) => {
        if (el._value?.disabled) return
        const rect = dragEl.getBoundingClientRect()
        isPressDown = true
        startX = e.clientX
        startY = e.clientY
        startWidth = el.offsetWidth
        startHeight = el.offsetHeight
        startTop = rect.top
        startLeft = rect.left
        isMove = false
        dragEl.classList.add(RESIZE_CONFIG.CLASS.ACTIVE)
        document.body.classList.add(RESIZE_CONFIG.CLASS.BODY_DRAGGING(direction))

        if (transition) {
          el.style.transition = 'none'
        }

        document.addEventListener('mousemove', onMouseMove)
        document.addEventListener('mouseup', onMouseUp)
      }

      const onResize = () => {
        if (el._value?.disabled) return
        runAnimation(el._value?.delay || 0, () => {
          resetDragElement(el) // 屏幕变化时，更新位置
        }) // 运行动画
      }

      dragEl.addEventListener('mousedown', onMouseDown)
      window.addEventListener('resize', onResize)

      el._onResize = onResize
    },

    updated(el: ResizeElement, binding: DirectiveBinding<ResizeDirectiveValue>) {
      const { value } = binding
      if (!el._dragEl) return

      el._value = value
      el._dragEl.style.display = value.disabled ? 'none' : ''
      el._dragEl.style.pointerEvents = value.disabled ? 'none' : ''

      runAnimation(el._value.delay || 0, () => {
        resetDragElement(el) // 更新位置
      }) // 运行动画
    },

    unmounted(el: ResizeElement) {
      el._dragEl?.remove()
      if (el._onResize) {
        window.removeEventListener('resize', el._onResize)
      }
    },
  })
}

export default {
  install(app: App) {
    resizeDirective(app)
  },
}
